using LuckyLang.AST.Expression;
using LuckyLang.AST.Expression.ValueTyre;
using LuckyLang.AST.Statement;
using sly.lexer;
using sly.parser.generator;
using sly.parser.parser;

namespace LuckyLang;

/// <summary>
/// ebnf csly:
/// eg:
/// set: IDENTIFIER SET[d] expr 
/// </summary>
[ParserRoot("root")]
public class Parser
{
    [Production("root: statement*")]
    public AST.AST Root(List<AST.AST> a) => new BlockStatement(a);

    
    [Operation((int)Lexer.LESSER,   Affix.InFix,Associativity.Right,10)]
    [Operation((int)Lexer.GREATER,  Affix.InFix,Associativity.Right,10)]
    [Operation((int)Lexer.EQUALS,   Affix.InFix,Associativity.Right,10)]
    [Operation((int)Lexer.DIFFERENT,Affix.InFix,Associativity.Right,10)]
    public AST.AST BinaryExpression(Expression left,Token<Lexer> operatorToken,Expression right) =>
        new Operation(left,operatorToken.TokenID,right);

    [Operation((int)Lexer.DOT,Affix.InFix,Associativity.Right,100)]
    public AST.AST DotExpr(Expression left,Token<Lexer> operatorToken,Expression right) =>
        new Operation(left,operatorToken.TokenID,right);

    [Operation((int)Lexer.PLUS, Affix.InFix,Associativity.Right,20)]
    [Operation((int)Lexer.MINUS,Affix.InFix,Associativity.Right,20)]
    public AST.AST NumOper1(Expression left,Token<Lexer> operatorToken,Expression right) =>
        new Operation(left,operatorToken.TokenID,right);

    [Operation((int)Lexer.TIMES, Affix.InFix,Associativity.Right,70)]
    [Operation((int)Lexer.DIVIDE,Affix.InFix,Associativity.Right,70)]
    public AST.AST NmExpr2(Expression left,Token<Lexer> operatorToken,Expression right) =>
        new Operation(left,operatorToken.TokenID,right);

    [Operation((int)Lexer.AND,Affix.InFix,Associativity.Right,10)]
    [Operation((int)Lexer.OR, Affix.InFix,Associativity.Right,10)]
    public AST.AST BoolOper(Expression left,Token<Lexer> oper,Expression right) =>
        new Operation(left,oper.TokenID,right);

    [Operation((int)Lexer.NOT,Affix.PreFix,Associativity.Right,100)]
    public AST.AST NotBool(Token<Lexer> oper,Expression expr) =>
        new Operation(null,oper.TokenID,expr);

    [Operation((int)Lexer.MINUS,Affix.PreFix,Associativity.Right,100)]
    public AST.AST MINUS(Token<Lexer> oper,Expression expr) =>
        new Operation(null,oper.TokenID,expr);
    

    #region primany

    [Operand]
    [Production("operand: primary")]
    public AST.AST Operand(AST.AST prim) => prim;

    [Production("primary: LPAREN[d] Parser_expressions RPAREN[d]")]
    public AST.AST LR(Expression prim) => prim;

    [Production("primary: STRING")]
    public AST.AST STRING(Token<Lexer> token) => new StringValue(token.Value);

    [Production("primary: INT")]
    public AST.AST INT(Token<Lexer> token) => new IntValue(token.IntValue);
    
    [Production("primary: DOUBLE")]
    public AST.AST DOUBLE(Token<Lexer> token) => new DoubleValue(double.Parse(token.Value));

    [Production("primary: IDENTIFIER")]
    public AST.AST IDENTIFIER(Token<Lexer> id) => new ID(id.Value);

    [Production("primary: TRUE")]
    public AST.AST BoolTrue(Token<Lexer> token) => new BoolValue(true);

    [Production("primary: FALSE")]
    public AST.AST BoolFalse(Token<Lexer> token) => new BoolValue(false);


    [Production("primary: PASS[d]")]
    public AST.AST Pass() => new IntValue(0);

    #endregion

    [Production("block: INDENT[d] statement* UINDENT[d]")]
    public AST.AST Block(List<AST.AST> a) => new BlockStatement(a);
    
    [Production("statement: set")]
    public AST.AST SET(AST.AST a) => a;
    
    [Production("set: IDENTIFIER SET[d] Parser_expressions")]
    public AST.AST Set(Token<Lexer> id,Expression expression) =>
        new SetStatement(new ID(id.Value),expression);
    
    [Production("statement: FOR[d] LPAREN[d] set Parser_expressions statement RPAREN[d] ':'[d] block")]
    public AST.AST For(SetStatement set,Expression expression,Statement statement,BlockStatement block)
        => new ForStatement(set,expression,statement,block);

    [Production("statement: WHILE[d] LPAREN[d] Parser_expressions RPAREN[d] ':'[d] block")]
    public AST.AST While(Expression expression,BlockStatement blockStatement)
        => new WhileStatement(expression,blockStatement);
    
    [Production("statement : IF[d] ifblock (ELIF[d] ifblock)* (ELSE[d] ':'[d] block)?")]
    public AST.AST If(IfBlock ifBlock,List<Group<Lexer,AST.AST>> elif,ValueOption<Group<Lexer,AST.AST>> Else)
    {
        var eGrp = Else.Match(
                              x => { return x; },() => { return null; });
        var elseBlock = eGrp?.Value(0) as BlockStatement;
        var a         = elif.Select(x => x.Value(0) as IfBlock).ToList();
        return new IfStatement(ifBlock,a,elseBlock);
    }
    [Production("ifblock: Parser_expressions ':'[d] block")]
    public AST.AST IfBlock(Expression expr,BlockStatement block)
        => new IfBlock(expr,block);

    [Production("statement: IMPORT[d] IDENTIFIER")]
    public AST.AST Import(Token<Lexer> id) => new ImportStatement(new ID(id.Value));

    [Production("statement: CLASS[d] IDENTIFIER INDENT[d] classinfo* UINDENT[d]")]
    public AST.AST Class(Token<Lexer> id,List<AST.AST> asts) 
        => new ClassInitStatement(new ID(id.Value),asts);

    [Production("classinfo: set")]
    public AST.AST ClassInfo_Set(AST.AST a) => a;

    [Production("classinfo: DEF[d] IDENTIFIER '('[d] IDENTIFIER* ')'[d] ':'[d] block")]
    public AST.AST ClassInfo_Def(Token<Lexer> id,List<Token<Lexer>> ids,BlockStatement blockStatement)
    {
        List<ID> a = new List<ID>();
        ids.ForEach(x => a.Add(new ID(x.Value)));
        return new DefInitStatement(new ID(id.Value),a,blockStatement);
    }
    
    [Production("statement: DEF[d] IDENTIFIER '('[d] IDENTIFIER* ')'[d] ':'[d] block")]
    public AST.AST Def(Token<Lexer> id,List<Token<Lexer>> ids,BlockStatement blockStatement)
    {
        List<ID> a = new List<ID>();
        ids.ForEach(x => a.Add(new ID(x.Value)));
        return new DefInitStatement(new ID(id.Value),a,blockStatement);
    }

    [Production("classinfo: IDENTIFIER")]
    public AST.AST ClassInfo_ID(Token<Lexer> a) => new ID(a.Value);
}